﻿#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StarFox.Graphics;
#endregion

namespace StarFox.ScreenComponents
{
    /// <summary>
    /// Implementation of an asteroid field.
    /// <para>Definition of Draw() method.</para>
    /// </summary>
    partial class AsteroidField
    {
        #region Draw

        /// <summary>
        /// Called when the ScreenComponent3D needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public override void Draw(GameTime gameTime)
        {
            for (int iz = NUM_SECTORS_Z - 1; iz >= 0; iz--)
            {
                for (int ix = 0; ix <= NUM_SECTORS_X - 1; ix++)
                {
                    if (sectorIsVisible[ix, iz])
                    {
                        #region Asteroids

                        for (int num = 0; num < sectorAsteroids[ix, iz].Count; num++)
                        {
                            IAsteroid asteroid = sectorAsteroids[ix, iz][num];

                            float distance = (asteroid.Position - ship.Position).Length();

                            if (distance > MAX_VIEW_DIST)
                            {
                                continue;
                            }
                            else if (distance > FADE_OUT_DIST)
                            {
                                float alpha = (float)Math.Pow((distance - FADE_OUT_DIST) / (MAX_VIEW_DIST - FADE_OUT_DIST), 2);
                                alpha = MathHelper.Clamp(1.0f - alpha, 0.0f, 1.0f);

                                if (asteroid is Asteroid)
                                    DrawAsteroid(asteroid as Asteroid, false, alpha);
                                else if (asteroid is PowerUp)
                                    DrawPowerUp(asteroid as PowerUp, false, alpha);
#if DEBUG
                                if (showInfo) { DrawBoundingSphere(asteroid); }
#endif
                            }
                            else if (distance > LOW_DETAIL_DIST)
                            {
                                if (asteroid is Asteroid)
                                    DrawAsteroid(asteroid as Asteroid, false, 1.0f);
                                else if (asteroid is PowerUp)
                                    DrawPowerUp(asteroid as PowerUp, false, 1.0f);
#if DEBUG
                                if (showInfo) { DrawBoundingSphere(asteroid); }
#endif
                            }
                            else
                            {
                                if (asteroid is Asteroid)
                                    DrawAsteroid(asteroid as Asteroid, true, 1.0f);
                                else if (asteroid is PowerUp)
                                    DrawPowerUp(asteroid as PowerUp, true, 1.0f);
#if DEBUG
                                if (showInfo) { DrawBoundingSphere(asteroid); }
#endif
                            }
                        }

                        #endregion

                        #region SmallAsteroids

                        for (int num = 0; num < sectorSmallAsteroids[ix, iz].Count; num++)
                        {
                            SmallAsteroid smallAsteroid = sectorSmallAsteroids[ix, iz][num];

                            float distance = (smallAsteroid.Position - ship.Position).Length();

                            if (distance > MAX_VIEW_DIST)
                            {
                                continue;
                            }
                            else if (distance > FADE_OUT_DIST)
                            {
                                float alpha = (float)Math.Pow((distance - FADE_OUT_DIST) / (MAX_VIEW_DIST - FADE_OUT_DIST), 2);
                                alpha = MathHelper.Clamp(1.0f - alpha, 0.0f, 1.0f);
                                DrawSmallAsteroid(smallAsteroid, alpha);
                            }
                            else
                            {
                                DrawSmallAsteroid(smallAsteroid, 1.0f);
                            }
                        }

                        #endregion
                    }
                }
            }
        }

        #endregion

        #region DrawAsteroid

        /// <summary>
        /// Draws an Asteroid object.
        /// </summary>
        /// <param name="asteroid">The Asteroid object.</param>
        /// <param name="high_low">Using a high or low detailed model.</param>
        /// <param name="alpha">The transparency level.</param>
        private void DrawAsteroid(Asteroid asteroid, bool high_low, float alpha)
        {
            Model model = (high_low) ? asteroidModels[asteroid.Index] : asteroidModelsLow[asteroid.Index];
            Matrix[] modelTransf = model.Tag as Matrix[];

            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            foreach (ModelMesh mesh in model.Meshes)
            {
                Matrix world = modelTransf[mesh.ParentBone.Index] * asteroid.World;

                foreach (Effect effect in mesh.Effects)
                {
                    asteroidEffect.WorldViewProjection = world * Camera.View * Camera.Projection;
                    asteroidEffect.World = world;
                    asteroidEffect.WorldInvTrans = Matrix.Transpose(Matrix.Invert(world));
                    asteroidEffect.CameraPosition = Camera.Position;
                    asteroidEffect.Alpha = alpha;

                    asteroidEffect.SetEffectParameters(effect);
                }

                mesh.Draw();
            }
        }

        #endregion

        #region DrawPowerUp

        /// <summary>
        /// Draws a PowerUp object.
        /// </summary>
        /// <param name="powerUp">The PowerUp object.</param>
        /// <param name="high_low">Using a high or low detailed model.</param>
        /// <param name="alpha">The transparency level.</param>
        private void DrawPowerUp(PowerUp powerUp, bool high_low, float alpha)
        {
            #region Draw2D - Glow

            Vector3 glowProjection = GraphicsDevice.Viewport.Project(powerUp.Position, Camera.Projection, Camera.View, Matrix.Identity);

            if ((glowProjection.Z >= 0) && (glowProjection.Z <= 1))
            {
                glowRectangle.Location = new Point((int)glowProjection.X, (int)glowProjection.Y);
                SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
                SpriteBatch.Draw(glowTexture, glowRectangle, null, PowerUp.PowerUpColor[powerUp.Index] * alpha,
                                 0.0f, new Vector2(glowTexture.Width / 2, glowTexture.Height / 2), SpriteEffects.None, 0);
                SpriteBatch.End();
            }

            #endregion

            #region Draw3D - Model

            Model model = (high_low) ? powerUpModels[powerUp.Index] : powerUpModelsLow[powerUp.Index];
            Matrix[] modelTransf = model.Tag as Matrix[];

            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            foreach (ModelMesh mesh in model.Meshes)
            {
                Matrix world = modelTransf[mesh.ParentBone.Index] * powerUp.World;

                foreach (Effect effect in mesh.Effects)
                {
                    powerUpEffect.WorldViewProjection = world * Camera.View * Camera.Projection;
                    powerUpEffect.World = world;
                    powerUpEffect.WorldInvTrans = Matrix.Transpose(Matrix.Invert(world));
                    powerUpEffect.CameraPosition = Camera.Position;
                    powerUpEffect.Alpha = alpha;

                    powerUpEffect.SetEffectParameters(effect);
                }

                mesh.Draw();
            }

            #endregion
        }

        #endregion

        #region DrawBoundingSphere

#if DEBUG
        private void DrawBoundingSphere(IAsteroid asteroid)
        {
            BoundingSphere sphere = AsteroidBoundingSphere(asteroid);
            Matrix auxWorld = Matrix.CreateScale(sphere.Radius) * Matrix.CreateTranslation(sphere.Center);

            Matrix[] modelTransf = new Matrix[boundingSphereModel.Bones.Count];
            boundingSphereModel.CopyAbsoluteBoneTransformsTo(modelTransf);

            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            RasterizerState oldState = GraphicsDevice.RasterizerState;
            RasterizerState newState = new RasterizerState() { FillMode = FillMode.WireFrame };
            GraphicsDevice.RasterizerState = newState;

            foreach (ModelMesh mesh in boundingSphereModel.Meshes)
            {
                Matrix world = modelTransf[mesh.ParentBone.Index] * auxWorld;

                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = world;
                    effect.View = Camera.View;
                    effect.Projection = Camera.Projection;
                }

                mesh.Draw();
            }

            GraphicsDevice.RasterizerState = oldState;
        }
#endif

        #endregion

        #region DrawSmallAsteroid

        /// <summary>
        /// Draws a SmallAsteroid object.
        /// </summary>
        /// <param name="smallAsteroid">The SmallAsteroid object.</param>
        /// <param name="alpha">The transparency level.</param>
        private void DrawSmallAsteroid(SmallAsteroid smallAsteroid, float alpha)
        {
            Model model = smallAsteroidModels[smallAsteroid.Model];
            Matrix[] modelTransf = model.Tag as Matrix[];

            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            foreach (ModelMesh mesh in model.Meshes)
            {
                Matrix world = modelTransf[mesh.ParentBone.Index] * smallAsteroid.World;

                foreach (Effect effect in mesh.Effects)
                {
                    asteroidEffect.WorldViewProjection = world * Camera.View * Camera.Projection;
                    asteroidEffect.World = world;
                    asteroidEffect.WorldInvTrans = Matrix.Transpose(Matrix.Invert(world));
                    asteroidEffect.CameraPosition = Camera.Position;
                    asteroidEffect.Alpha = alpha;

                    asteroidEffect.SetEffectParameters(effect);
                }

                mesh.Draw();
            }
        }

        #endregion
    }
}
